﻿// Copyright 2013 Ronald Schlenker, www.technewlogic.de.
// 
// This file is part of Byond.
// 
// Byond is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 2 of the License, or
// (at your option) any later version.
// 
// Byond is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with Byond.  If not, see <http://www.gnu.org/licenses/>.

using System;
using System.Collections.Generic;
using System.Reflection;
using Autofac.Core;
using Byond.Frontend.Messages;
using Byond.HostObjects;
using Byond.Logging;
using Byond.Messages;
using Byond.Persistence;
using NLog;
using Utils;
using Utils.DialogManagement;
using Utils.Messaging;

namespace Byond.Frontend
{
	class EnsembleRunnerWorkflow : IEnsembleRunner
	{
		private readonly IDialogManager _dialogManager;
		private readonly IEnsembleQueryService _ensembleQueryService;
		private readonly RuntimeScope _runtimeScope;
		private readonly PersistenceManager _persistenceManager;
		private readonly Logger _logger;
		private readonly Lazy<IUserLogger> _userLogger;
		private readonly UiDispatcher _dispatcher;

		public event MessageEventHandler SendMessage;

		public void Run(string patchFileName, Lazy<IEnumerable<PatchDto>> pendingRestoreInfo = null)
		{
			var ensemble = _ensembleQueryService.GetByFileName(patchFileName);

			var run = new Action(() => Run(ensemble, pendingRestoreInfo));
			var update = new Action(() =>
				{
					var restoreInfos = _persistenceManager.PersistCurrentState();
					Run(ensemble, restoreInfos);
				});

			var rootPatch = _runtimeScope.Current.PatchManager.RootPatch;
			if (rootPatch != Patch.Empty)
			{
				// pendingRestoreInfo == null: OpenSession oder Run Fall
				if (patchFileName.GetClassName() == rootPatch.GetType().Name 
					&& pendingRestoreInfo == null)
				{
					var dialog = _dialogManager.CreateMessageDialog(
						"The state of the current patch will be overwritten.",
						"Stop current patch and replace it?",
						DialogMode.YesNoCancel);
					dialog.YesText = "Update";
					dialog.NoText = "Reset";
					dialog.Yes = update;
					dialog.No = run;
					dialog.Show();
				}
				else
				{
					var dialog = _dialogManager.CreateMessageDialog(
						"The state of the current patch will be overwritten.",
						"Stop current patch and replace it?",
						DialogMode.YesNo);
					dialog.Yes = run;
					dialog.Show();
				}
			}
			else
				run();
		}

		public void Stop()
		{
			DoStop();
		}

		public void ReportRuntimeError(Exception exception)
		{
			_dispatcher.Invoke(() => DoStop(exception));
		}

		private void DoStop(Exception exception = null)
		{
			_runtimeScope.Reset();

			if (exception != null)
			{
				const string errorDescription = "An Error occured in the patch code";
				_logger.ErrorException(errorDescription, exception);
				var dialog = _dialogManager
					.CreateMessageDialog(
						exception.ToString(),
						errorDescription,
						DialogMode.Ok);

				dialog.Ok = () => SendMessage(new EnsembleStoppedMessage());
				dialog.Show();
			}
			else
				SendMessage(new EnsembleStoppedMessage());

			_userLogger.Value.Log("Ensemble stopped.");
		}

		private void Run(Ensemble ensemble, Lazy<IEnumerable<PatchDto>> pendingRestoreInfo)
		{
			// zuerst prüfen, dann richtiges Patch erstellen
			_runtimeScope.PrepareFutureContext();
			try
			{
				_runtimeScope.MostFuture.PatchManager.CreatePatch(ensemble);

				_runtimeScope.MostFuture.PatchManager.RunCreatedPatch();
				_runtimeScope.ApplyFutureContext();

				if (pendingRestoreInfo != null)
				{
					_runtimeScope.Current.PatchManager.RestoreState(
						pendingRestoreInfo.Value);
				}

				SendMessage(new EditDocumentRequest(ensemble.FileName));
			}
			catch (Exception exception)
			{
				var unwrapedException = UnwrapException(exception);
				_dialogManager
					.CreateMessageDialog(
						unwrapedException.Message,
						"Could not compile or run the patch",
						DialogMode.Ok)
					.Show();

				_userLogger.Value.Log(exception.ToString());

				_runtimeScope.DiscardFutureContext();
				return;
			}

			SendMessage(new EnsembleRunningMessage());

			_userLogger.Value.Log("Ensemble created: " + ensemble.FileName);
		}

		private static Exception UnwrapException(Exception originalException)
		{
			var ex = originalException;
			while (true)
			{
				if (ex is DependencyResolutionException)
					ex = ex.InnerException;
				else if (ex is TargetInvocationException)
					ex = ex.InnerException;
				else
					return ex;
			}
		}
	}
}